# -*- coding: utf-8 -*-
__author__ = 'lztkdr'
__date__ = '2020/4/16 16:23'

import numpy as np
import pandas as pd
from sklearn import preprocessing as pre
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
# 正规方程线性模型、梯度下降线性模型、岭回归模型、逻辑回归
from sklearn.linear_model import LinearRegression, SGDRegressor, Ridge, LogisticRegression
# 损失函数/均方误差，专对回归模型的性能评估
from sklearn.metrics import mean_squared_error

"""
分类算法具有的 分类指标
此处针对 逻辑回归 做分类指标计算
"""
from sklearn.metrics import classification_report

"""
	sklearn.linear_model.LinearRegression  正规方程

	sklearn.linear_model.SGDRegressor   梯度下降
	
	sklearn.linear_model.Ridge 岭回归

	线性模型: 试图学得一个通过 属性的线性组合 来进行预测的函数。
	线性回归：
		定义：线性回归通过一个或者多个自变量与因变量之间之间进行建模的回归分析。其中特点为一个或多个称为回归系数的模型参数的线性组合
		一元线性回归：涉及到的变量只有一个
		多元线性回归：涉及到的变量两个或两个以上

	预测结果与真实值是有一定的误差。
	
	sklearn回归评估API:
	
		sklearn.metrics.mean_squared_error   均方误差 越小越好。
		
		mean_squared_error(y_true, y_pred)
			均方误差回归损失
			y_true:真实值
			y_pred:预测值
			return:浮点数结果
			
		注：真实值，预测值为标准化之前的值
	----------------------------------------------------========
		回归特点：
			线性回归器是最为简单、易用的回归模型。
			从某种程度上限制了使用，尽管如此，在不知道特征之
			间关系的前提下，我们仍然使用线性回归器作为大多数
			系统的首要选择。
		
		小规模数据：LinearRegression(不能解决拟合问题)以及其它
		大规模数据：SGDRegressor

	----------------------------------------------------========
        	特征选择 主要方法（三大武器）：
					   Filter(过滤式):VarianceThreshold 低方差特征
	     	           Embedded(嵌入式)：正则化、决策树、神经网络
		               Wrapper(包裹式)
	--------------------------------------------------------------
	回归解决过拟合的方式：
		线性回归 LinearRegression 容易出现过拟合，为了把训练数据表现的更好，使用 L2正则化。
		L2正则化 Ridge岭回归 带有正则化的线性回归，通过调整正则化力度，解决过拟合。
	--------------------------------------------------------------
	sklearn.linear_model.Ridge(alpha=1.0)
		具有l2正则化的线性最小二乘法		
		alpha:正则化力度
		coef_:回归系数
	--------------------------------------------------------------
	线性回归 LinearRegression与Ridge对比
	岭回归：
		回归得到的回归系数更符合实际，更可靠。
		另外，能让估计参数的波动范围变小，变的更稳定。在存在病态数据偏多的研究中有较大的实用价值。（病态数据，也称异常数据。）
	--------------------------------------------------------------
	逻辑回归：
		解决二分类问题的利器。
		逻辑回归的sigmoid可以将输入转换成一个概率值（0-1）之间。
		
	sklearn.linear_model.LogisticRegression(penalty=‘l2’, C = 1.0)
	Logistic回归分类器
	coef_：回归系数

	penalty=L2 l2正则化，解决过拟合的方式。
	
	应用场景：
		广告点击率
		判断用户的性别
		预测用户是否会购买给定的商品类
		判断一条评论是正面的还是负面的
	--------------------------------------------------------------
	LogisticRegression总结

		应用：广告点击率预测、电商购物搭配推荐
	
		优点：适合需要得到一个分类概率的场景
		
		缺点：当特征空间很大时，逻辑回归的性能不是很好
		（看硬件能力）

"""

x_train, x_test, y_train, y_test = None, None, None, None
x_std, y_std = None, None


def init():
	import sklearn
	print("sklearn version:", sklearn.__version__)
	# 获取波士顿房价数据集
	ds = load_boston()

	global x_train, x_test, y_train, y_test, x_std, y_std

	# 分割数据集到训练集与测试集
	# random_state 若为整数时，每次生成的数据都相同,若为None时，每次生成的数据都是随机，可能不一样
	x_train, x_test, y_train, y_test = train_test_split(ds.data, ds.target, test_size=0.2, random_state=None)
	print('x_train:', x_train[:10], '\n--------------------------\ny_test：', y_test[:10])

	print("--------------------------")
	# 进行标准化处理(?)训练集要标准化，目标值要不要标准化处理？ 线性模型，输入值标准化了，对应的
	# 特征值和目标值是都必须进行标准化处理, 实例化两个标准化
	x_std = pre.StandardScaler()
	x_train = x_std.fit_transform(x_train)
	x_test = x_std.transform(x_test)

	y_std = pre.StandardScaler()
	# sk 0.18 版本 传入一维、二维都可以， 超过0.19 时 要求必须是二维
	# 特征工程的转换器，输入值 与 输出值 传入时，需要是二维。
	# .reshape(-1, 1) 一维 转 二维
	y_train = y_std.fit_transform(y_train.reshape(-1, 1))
	y_test = y_std.transform(y_test.reshape(-1, 1))

	print(' StandardScaler x_train:', x_train[:10],
	      '\n--------------------------\ny_test：', y_test[:10])
	print("--------------------------")
	return None


def linear():
	"""
	# 正规方程求解方式预测房价
	:return:
	"""
	# 正规方程求解方式预测结果
	model = LinearRegression()
	model.fit(x_train, y_train)

	# 训练好模型后，就能够找出 intercept_与 coef_ 的两个参数。
	# coef_ ：系数，回归模型的有些参数。
	# intercept_ : 截距，回归模型的有些参数。
	print('coef_:', model.coef_)
	print('intercept_:', model.intercept_)
	print("--------------------------")

	# 线性模型最好 不用准确率 来作为评估模型的指标
	print("正规方程 预测准确率：", model.score(x_test, y_test))

	# 测试集的 预测价格
	# y_real_predict = y_std.inverse_transform(model.predict(x_test))
	y_predict = model.predict(x_test)
	y_real_predict = y_std.inverse_transform(y_predict)
	print('正规方程求解方式预测房价 测试集的预测价格:', y_real_predict[::20])
	print("--------------------------")
	print("正规方程的均方误差：", mean_squared_error(y_std.inverse_transform(y_test), y_real_predict))
	return None


def SGD():
	"""
	梯度下降去进行房价预测
	:return:
	"""
	# 梯度下降去进行房价预测
	sgd = SGDRegressor()

	sgd.fit(x_train, y_train)

	# 训练好模型后，就能够找出 intercept_与 coef_ 的两个参数。
	# coef_ ：系数，回归模型的有些参数。
	# intercept_ : 截距，回归模型的有些参数。
	print('coef_:', sgd.coef_)
	print('intercept_:', sgd.intercept_)
	print("--------------------------")

	# 线性模型最好 不用准确率 来作为评估模型的指标
	print("梯度下降 预测准确率：", sgd.score(x_test, y_test))

	# 预测测试集的房子价格
	y_real_predict = y_std.inverse_transform(sgd.predict(x_test))

	print("梯度下降去进行房价预测 测试集的预测价格：", y_real_predict[::20])
	print("--------------------------")
	print("梯度下降的均方误差：", mean_squared_error(y_std.inverse_transform(y_test), y_real_predict))
	return None


def ridge():
	"""
	岭回归 进行房价预测
	:return:
	"""
	# 梯度下降去进行房价预测
	# alpha 取值： 0-1 之间小数的值 或者 1-10 之间的整数
	rd = Ridge(alpha=0)

	import os.path
	if os.path.exists('./rd.pkl'):
		# 保存训练好的模型
		# from sklearn.externals import joblib  # 弃用警告
		import joblib
		rd = joblib.load('./rd.pkl')

	rd.fit(x_train, y_train)

	# 训练好模型后，就能够找出 intercept_与 coef_ 的两个参数。
	# coef_ ：系数，回归模型的有些参数。
	# intercept_ : 截距，回归模型的有些参数。
	print('coef_:', rd.coef_)
	print('intercept_:', rd.intercept_)
	print("--------------------------")

	# 保存训练好的模型
	# from sklearn.externals import joblib  # 弃用警告
	import joblib
	joblib.dump(rd, "./rd.pkl")

	# 线性模型最好 不用准确率 来作为评估模型的指标
	print("岭回归 预测准确率：", rd.score(x_test, y_test))

	# 预测测试集的房子价格
	y_real_predict = y_std.inverse_transform(rd.predict(x_test))

	print("梯度下降去进行房价预测 测试集的预测价格：", y_real_predict[::20])
	print("--------------------------")
	print("岭回归的均方误差：", mean_squared_error(y_std.inverse_transform(y_test), y_real_predict))

	return None


"""
	良／恶性乳腺癌肿数据

	原始数据的下载地址：
	https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/
	
	数据描述
	（1）699条样本，共11列数据，第一列用语检索的id，后9列分别是与肿瘤
	相关的医学特征，最后一列表示肿瘤类型的数值。
	（2）包含16个缺失值，用”?”标出。 

	良性：458  65.5%
	恶性：241  34.5%
											
	----------------------------------------
	
   1. Sample code number            id number
   2. Clump Thickness               1 - 10
   3. Uniformity of Cell Size       1 - 10
   4. Uniformity of Cell Shape      1 - 10
   5. Marginal Adhesion             1 - 10
   6. Single Epithelial Cell Size   1 - 10
   7. Bare Nuclei                   1 - 10
   8. Bland Chromatin               1 - 10
   9. Normal Nucleoli               1 - 10
  10. Mitoses                       1 - 10
  11. Class:                        (2 for benign, 4 for malignant)
	
	1.样品编号ID编号
    2.团块厚度1-10
    3.像元大小的均匀性1-10
    4.细胞形状的均匀性1-10
    5.边缘附着力1-10
    6.单个上皮细胞大小1-10
    7.裸核1-10
    8.淡色染色质1-10
    9.正常核仁1-10
   10. Mitoses 1-10
   11.等级：（2代表良性，4代表恶性）

	判定这个人是恶性的概率是多少？
"""


def logistic():
	"""
	逻辑回归 做 二分类 进行癌症预测（根据细胞的属性特征）
	:return: NOne
	"""
	# 构造列标签名字
	column = ['Sample code number', 'Clump Thickness', 'Uniformity of Cell Size', 'Uniformity of Cell Shape',
	          'Marginal Adhesion', 'Single Epithelial Cell Size', 'Bare Nuclei', 'Bland Chromatin', 'Normal Nucleoli',
	          'Mitoses', 'Class']

	file_name = "https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data"
	file_name = "./data/breast-cancer-wisconsin.data"
	# 读取数据
	df = pd.read_csv(file_name, names=column)

	print('良／恶性乳腺癌肿数据：\n', df)
	print("--------------------------")

	# 缺失值进行处理
	df = df.replace(to_replace='?', value=np.nan)

	df = df.dropna()

	# 进行数据的分割
	# x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25)
	x_train, x_test, y_train, y_test = train_test_split(df[column[1:10]], df[column[10]], test_size=0.25,
	                                                    random_state=0)

	# 进行标准化处理
	std = pre.StandardScaler()

	# 分类问题，只对特征值进行标准化，目标值则不用标椎化
	x_train = std.fit_transform(x_train)
	x_test = std.transform(x_test)

	# 逻辑回归预测
	lg = LogisticRegression(C=1.0)

	lg.fit(x_train, y_train)

	# 训练好模型后，就能够找出 intercept_与 coef_ 的两个参数。
	# coef_ ：系数，回归模型的有些参数。
	# intercept_ : 截距，回归模型的有些参数。
	print('coef_:', lg.coef_)
	print('intercept_:', lg.intercept_)
	print("--------------------------")

	y_predict = lg.predict(x_test)

	print("逻辑回归 准确率：", lg.score(x_test, y_test))
	print("--------------------------")

	print("逻辑回归 召回率：\n", classification_report(y_test, y_predict, labels=[2, 4], target_names=["良性", "恶性"]))


if __name__ == '__main__':
	init()
	print("====================================================================================================")
	# 正规方程 预测房价
	linear()
	print("====================================================================================================")
	# 梯度下降 房价预测
	SGD()
	print("====================================================================================================")
	# 岭回归 房价预测
	ridge()
	print("====================================================================================================")
	# 逻辑回归 做 二分类 进行癌症预测（根据细胞的属性特征）
	logistic()
	# 准确率： 0.947 ，对于产品来说，是没有多大意义。
	# 恶性的召回率(recall)：0.92 ，也就是92%，说明剩余的 有8% 预测应该是恶性，反而被预测成良性，没有被检测出来。
	print("====================================================================================================")
# import joblib 加载、保存模型
# predict 之前检测pkl文件是否存在，存在则 joblib.load 加载模型，然后进行predict，不存在模型文件，则 fit训练之后 就可以 joblib.dump保存模型。
